package cn.itcast.zt;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Created by zhangtian on 2017/4/25.
 */
@Configuration
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String host ;
    @Value("${spring.redis.port}")
    private Integer port ;
    @Value("${spring.redis.password}")
    private String password ;
    @Value("${spring.redis.pool.maxIdleConnections}")
    private Integer maxIdleConnections ;
    @Value("${spring.redis.pool.minIdleConnections}")
    private Integer minIdleConnections ;
    @Value("${spring.redis.pool.maxConnections}")
    private Integer maxConnections ;
    @Value("${spring.redis.pool.maxWaitTime}")
    private Long maxWaitTime ;
    @Value("${spring.redis.timeout}")
    private Integer timeout ;


    @Bean
    public JedisPoolConfig initJedisPoolConfig() {
        // 池化配置
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig() ;
        jedisPoolConfig.setMaxIdle(maxIdleConnections);
        jedisPoolConfig.setMinIdle(minIdleConnections);
        jedisPoolConfig.setMaxTotal(maxConnections);
        jedisPoolConfig.setTestOnBorrow(true);
        jedisPoolConfig.setTestOnReturn(true);
        jedisPoolConfig.setTestWhileIdle(true);
        jedisPoolConfig.setNumTestsPerEvictionRun(10);
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(60000L);
        jedisPoolConfig.setMaxWaitMillis(maxWaitTime);

        return jedisPoolConfig ;
    }

    @Bean
    public RedisConnectionFactory initRedisConnectionFactory(JedisPoolConfig jedisPoolConfig){

        // 单机版Redis
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration() ;
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));

        // 集群版本以及哨兵模式的单独查资料 ......

        // 配置绑定
        JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder() ;
        JedisClientConfiguration jedisClientConfiguration = jedisClientConfigurationBuilder.usePooling().poolConfig(jedisPoolConfig).build() ;

        return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration) ;
    }

    /**
     * 功能描述: <br>
     * 〈实例化JedisPool 单机版本〉
     * @Param: [jedisPoolConfig]
     * @Return: redis.clients.jedis.JedisPool
     * @Author: zhangtian105
     * @Date: 2020/11/18 20:37
     */
    @Bean
    public JedisPool initJedisPool(JedisPoolConfig jedisPoolConfig){

        if(StringUtils.isNotEmpty(password)) {
            return new JedisPool(jedisPoolConfig, host, port, timeout, password);
        }

        return new JedisPool(jedisPoolConfig, host, port, timeout);
    }

    /**
     * 功能描述: <br>
     * 〈实例化JedisCluster 集群分片版本〉
     * @Param: [jedisPoolConfig]
     * @Return: redis.clients.jedis.JedisCluster
     * @Author: zhangtian105
     * @Date: 2020/11/18 20:47
     */
    /*@Bean
    public JedisCluster initJedisCluster(JedisPoolConfig jedisPoolConfig) {
        Set<HostAndPort> nodeSet = new HashSet<>() ;
        nodeSet.add(new HostAndPort("localhost", 6380)) ;
        // ......... 集群分片模式就把多个host和ip配置进去

        JedisCluster jedisCluster = new JedisCluster(nodeSet, 2000, 3000, 1, password, jedisPoolConfig) ; // jedisPoolConfig根据集群配置定制化
        return jedisCluster ;
    }*/

    /**
     * 实例化 RedisTemplate 对象
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>() ;
        initDomainRedisTemplate(redisTemplate, redisConnectionFactory);

        return  redisTemplate ;
    }

    /**
     * 设置数据存入 redis 的序列化方式
     * @param redisTemplate
     * @param factory
     */
    private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
        // 配置连接工厂
        redisTemplate.setConnectionFactory(factory);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value（默认使用jdk序列化）
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域，field、get和set，以及修饰符范围，ANY表示都有报货private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 置顶序列化输入的类型，类必须是非final修饰的，final修饰的类  比如String Integer都会抛异常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);

        jackson2JsonRedisSerializer.setObjectMapper(om);

        //定义key序列化方式
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型会出现异常信息;需要我们上面的自定义key生成策略，一般没必要

        // 使用StringRedisSerializer来序列化和反序列化redis的key
        redisTemplate.setKeySerializer(redisSerializer);
        // 值采用json序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

        // 设置hash key和value的序列化模式
        redisTemplate.setHashKeySerializer(redisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
    }

    /**
     * 实例化 ValueOperations 对象,可以使用 String 操作
     * @param redisTemplate
     * @return
     */
    @Bean(name = "jsonValueOperations")
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return  redisTemplate.opsForValue() ;
    }

    /**
     * 这个和上面序列化是最关键的，后面的几个方法用不到可以去掉
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    // @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }


    /**
     * 实例化 ValueOperations 对象,可以使用 String 操作
     * 这里不用上面的RedisTemplate  是因为上面的这个RedisTemplate的value序列化使用jackson序列化，会导致存入的数据无缘无故多出了双引号，如： "1234"，而不是1234
     * 这样会引起删除分布式锁的lua表达式无法删除数据，传的值必须是"\"1234\"",这样才能删除，故而使用StringRedisTemplate来进行数据存储
     * @param stringRedisTemplate
     * @return
     */
    @Bean(value = "stringValueOperations")
    public ValueOperations<String, String> valueStringOperations(StringRedisTemplate stringRedisTemplate) {
        return  stringRedisTemplate.opsForValue() ;
    }
}
